/*
    fracgen-c : Fractal Generator (using concurrency)
    Copyright (C) 2010 Arpit Sud, Sri Teja Basava & Sidartha Gracias

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// Import Java libraries
import java.awt.*;
import java.awt.event.*;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.TextEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
 
// Import custom classes
import fractal.*;
import util.*;

public class FracgenUI extends Frame{
	/**
	 * @param args
	 */
	private static FractalGenerator fg;
	private static FractalImage fi;
	private int mouseEventFlag = 3; // 1 is Zoom In, 2 is Zoom Out, 3 is Scroll
	double r,i; // Real & Imaginary parts stored as double for generation on Julia Set
	TextField Real = new TextField("    "); // Real part of the complex number on UI
	TextField Img = new TextField("    "); // Imag part of the complex number on UI
	Button SetParam = new Button("Set"); // Button to change parameters of the Julia Set
	String timeForCalc;
	private static Label timeForCalcLabel;
	private static Label percentCompleteLabel;
	
	public static void main(String[] args) {
		FracgenUI app = new FracgenUI();
		app.setVisible(true);
	}
	
	// Exit the UI Window
	public void exit() { 
		setVisible(false); 
		dispose(); 
		System.exit(0); 
	}
	
	// Redefining the Repaint method
	public void repaint() {
		// New Thread to refresh % completion
		Thread t = new Thread(new keepRefreshing());
		t.start();
		long calcTime;
		// Calling Fractal Generator
		calcTime = fg.generate();
		
		timeForCalc = (new Long(calcTime).toString().concat(" miliseconds for calculation"));
		timeForCalcLabel.setText(timeForCalc);
		timeForCalcLabel.repaint();
		// Calculation done, interrupt the refresh thread
		t.interrupt();
		
        int percentComplete = fg.getPercentageComplete();
        percentCompleteLabel.setText(new Integer(percentComplete).toString().concat("% completed"));
        percentCompleteLabel.repaint();
        // Draw the Fractal Image
		fi.repaint();
	}

	public FracgenUI() { // Constructor
		super("Fractal Image Generator (using Concurrency)");
		fg = new FractalGenerator();

	    displayToolbars();
	    displayFI();
		
		Dimension imageSize = fi.getPreferredSize();
		setSize(imageSize.width+400,imageSize.height+100);
		// To exit the window
		addWindowListener(new WindowAdapter() { 
			public void windowClosing(WindowEvent e) { 
				exit(); 
			} 
		}); 
	}
	private void displayFI() {  // Initialize display of Fractal Image
		ComplexPlane cp = fg.getComplexPlane();
		
		fi = new FractalImage(cp);

		// Listen to mouse events when user clicks on the image
		fi.addMouseListener(new MouseAdapter(){
			public void mouseClicked(MouseEvent e) {
				ComplexPlane cp = fg.getComplexPlane();
				if (e.getButton() == MouseEvent.BUTTON1) {
					cp.setCenter(e.getX(), e.getY());
					if (mouseEventFlag==1) // Zoom In
					{
						cp.setScale(cp.getRealAxisScale()/2, 
							cp.getImaginaryAxisScale()/2);
					}
					else if (mouseEventFlag==2) // Zoom Out
					{
						cp.setScale(cp.getRealAxisScale()*2, 
							cp.getImaginaryAxisScale()*2);
					}
					repaint();
				}
			}
		});
		repaint();
		add("Center",fi);
	}
	
	private static class keepRefreshing implements Runnable {
		int percentComplete = 0;
		public void run()
		{
			try {
	            while(true) // keep refreshing % complete until interrupted
	            {
	                 Thread.sleep(10);
	                 percentComplete = fg.getPercentageComplete();
	                 percentCompleteLabel.setText(new Integer(percentComplete).toString().concat("% completed"));
	                 percentCompleteLabel.repaint();
	            }
	        } catch (InterruptedException e) {

	        }
			
		}
	}
	
	private void saveImageAs() 
	{
		FileDialog fd = new FileDialog(this, "Save Image As...", FileDialog.SAVE);
		fd.setFile("*.png");
        fd.setDirectory(System.getProperty("user.home"));
        fd.setVisible(true);
        String fullpath = fd.getDirectory() + fd.getFile();
        fd.dispose();
        fg.saveImage(fullpath);
	}
	private void saveParameters()
	{
		FileDialog fd = new FileDialog(this, "Save Parameter file", FileDialog.SAVE);
		fd.setFile("*.txt");
        fd.setDirectory(System.getProperty("user.home"));
        fd.setVisible(true);
        String fullpath = fd.getDirectory() + fd.getFile();
        fd.dispose();
        fg.saveParameters(fullpath);
	}
	
	private void setParameters() // Change the parameters of the Julia Set
	{
		Fractal f = new Julia(new Complex(r,i));
		fg.setFractal(f);
		ComplexPlane cp = fg.getComplexPlane();
		cp.reset();
		repaint();
	}

	private void displayToolbars() {
		// Event handler for Top Toolbar
		ActionListener topTListener = new ActionListener() {
	        public void actionPerformed(ActionEvent ae) {
	        	String item = ae.getActionCommand();
	        	if (item.equals("Reset")) {
	        		ComplexPlane cp = fg.getComplexPlane();
	        		cp.reset();
	        		repaint();
	        	}
	        	else if (item.equals("Save Image"))
	        		saveImageAs();
	        	else if (item.equals("Save Parameters"))
	        		saveParameters();
	        }
	      };
	      
	      // Event handlers for Real & Imaginary text boxes
	      TextListener l = new TextListener() {
			
			public void textValueChanged(TextEvent e) 
			{
				if(e.getSource()== Real)
					r = Double.parseDouble(Real.getText());					
				else
					i = Double.parseDouble(Img.getText());
			}
		};
	    
		// Event handlers for Zoom & Scroll Radio Buttons
	    ItemListener zoomChoiceListener = new ItemListener() {
	    	public void itemStateChanged(ItemEvent e) {
	    		Checkbox zoomChoice = (Checkbox)e.getSource();
	    		String result = zoomChoice.getLabel();
	    		if (result.equals("Zoom Out"))
	    			mouseEventFlag = 2;
	    		else if (result.equals("Zoom In"))
	    			mouseEventFlag = 1;
	    		else if (result.equals("Scroll"))
	    			mouseEventFlag = 3;
	    	}
	    };
	    
	    // Event handler for Fractal selection Drop-down
	    ItemListener fractalChoiceListener = new ItemListener()
	    {
	    	public void itemStateChanged(ItemEvent e)
	    	{
	    		Choice fractalChoice = (Choice)e.getSource();
	    		int result = fractalChoice.getSelectedIndex();
    			// Real & Imag text boxes and Set Button should be enabled only for Julia Sets
	    		Real.setEnabled(false);
    			Img.setEnabled(false);
    			SetParam.setEnabled(false);
	    		if(result == 0)
	    		{
	        		Fractal f = new Mandelbrot();
	        		fg.setFractal(f);
	    			ComplexPlane cp = fg.getComplexPlane();
	    			cp.reset();
	    			repaint();
	    		}
	    		else if(result == 1)
	    		{
	    			Fractal f = new Julia(new Complex(-0.8,0.156));
	    			/* Other well-known Julia sets */
	    			//Fractal f = new Julia(new Complex(-0.835,-0.2321));
	    			//Fractal f = new Julia(new Complex(0.285,0.0));
	    			//Fractal f = new Julia(new Complex(0.285,0.01));
	    			//Fractal f = new Julia(new Complex(-0.70176,-0.3842));
	    			//Fractal f = new Julia(new Complex(-0.8,0.156));
	    			//Fractal f = new Julia(new Complex(0.45,0.1428));
	    			//Fractal f = new Julia(new Complex(0.777,0.012));
	    			Real.setText("-0.8");
	    			Img.setText("0.156");
	    			fg.setFractal(f);
	    			ComplexPlane cp = fg.getComplexPlane();
	    			cp.reset();
        			repaint();
        			// Enable Text boxes & buttons exclusively for Julia Set
        			Real.setEnabled(true);
        			Img.setEnabled(true);
        			SetParam.setEnabled(true);
	    		}
	    		else if(result == 2)
	    		{
	        		Fractal f = new BurningShip();
	        		fg.setFractal(f);
	    			ComplexPlane cp = fg.getComplexPlane();
	    			cp.reset();
	    			repaint();
	    		}
	    	}
	    };
	    ItemListener numThreadChoiceListener = new ItemListener() {
	    	public void itemStateChanged(ItemEvent e)
	    	{
	    		Choice numThreadChoice = (Choice)e.getSource();
	    		int result = numThreadChoice.getSelectedIndex()+1;
	    		if (result>0 && result <17)
	    			fg.setNumThreads(result);
	    	}
	    };
	    
	    Panel topToolbar = new Panel();
	    topToolbar.setLayout(new FlowLayout(FlowLayout.LEFT));

	    // Drop-down for fractal types
	    Choice fractalChoice = new Choice();
	    fractalChoice.add("Mandelbrot Set");
	    fractalChoice.add("Julia Set");
	    fractalChoice.add("Burning Ship Fractal");
	    fractalChoice.addItemListener(fractalChoiceListener);
	    topToolbar.add(fractalChoice);

	    topToolbar.add(Real);
	    topToolbar.add(Img);
	    topToolbar.add(SetParam);
	    Real.addTextListener(l);
	    Img.addTextListener(l);
	    SetParam.addActionListener(topTListener);
	    Real.setEnabled(false);
	    Img.setEnabled(false);
	    SetParam.setEnabled(false);
	    Real.setBounds(getX(), getY(), 200, 200);
	    
	    // Reset Button
	    Button resetButton = new Button("Reset");
	    resetButton.addActionListener(topTListener);
	    topToolbar.add(resetButton);
	    
	    // Radio buttons for Zoom & Scroll
	    CheckboxGroup zoomGroup = new CheckboxGroup();
	    Checkbox zoomInChkBox = new Checkbox("Zoom In",zoomGroup,false);
	    Checkbox zoomOutChkBox = new Checkbox("Zoom Out",zoomGroup,false);
	    Checkbox scrollChkBox = new Checkbox("Scroll",zoomGroup,true);
	    zoomInChkBox.addItemListener(zoomChoiceListener);
	    zoomOutChkBox.addItemListener(zoomChoiceListener);
	    scrollChkBox.addItemListener(zoomChoiceListener);
	    topToolbar.add(zoomInChkBox);
	    topToolbar.add(zoomOutChkBox);
	    topToolbar.add(scrollChkBox);
	    
	    // Save Image & Save Parameters Button
	    Button saveImageButton = new Button("Save Image");
	    saveImageButton.addActionListener(topTListener);
	    topToolbar.add(saveImageButton);
	    Button saveParaButton = new Button("Save Parameters");
	    saveParaButton.addActionListener(topTListener);
	    topToolbar.add(saveParaButton);
	    
	    // Choose the number of threads to be spawned
	    Choice numThreadChoice = new Choice();
	    for (int tempCounter=1; tempCounter <=16; tempCounter++)
	    {
	    	if(tempCounter==1)
	    		numThreadChoice.add((Integer.toString(tempCounter))+" Thread");
	    	else
	    		numThreadChoice.add((Integer.toString(tempCounter))+" Threads");
	    }
	    numThreadChoice.addItemListener(numThreadChoiceListener);
	    numThreadChoice.select("1");
	    topToolbar.add(numThreadChoice);
	    
	    add(topToolbar, BorderLayout.NORTH);
	    
//		Right Toolbar
		ActionListener rightTListener = new ActionListener() {
	        public void actionPerformed(ActionEvent ae) {
	        	String item = ae.getActionCommand();
	        	if (item.equals("Set"))
	        		setParameters();
	        }
		};

	    Panel rightToolbar = new Panel();
	    rightToolbar.setLayout(new FlowLayout(FlowLayout.CENTER));
	    // Adding the real & Imaginary text boxes to right toolbar
	    rightToolbar.add(new Label("Real"));
	    rightToolbar.add(Real);
	    rightToolbar.add(new Label("Img"));
	    rightToolbar.add(Img);
	    rightToolbar.add(SetParam);
	    Real.addTextListener(l);
	    Img.addTextListener(l);
	    SetParam.addActionListener(rightTListener);
	    Real.setEnabled(false);
	    Img.setEnabled(false);
	    SetParam.setEnabled(false);

	    // Event handler for B&W or Color Radio Button
	    ItemListener BWorColorChkBoxListener = new ItemListener() {
	    	public void itemStateChanged(ItemEvent e) {
	    		Checkbox BWorColorChkBox = (Checkbox)e.getSource();
	    		String result = BWorColorChkBox.getLabel();
	    		if (result.equals("B&W"))
	    		{
	    			fg.makeBW();
	    			repaint();
	    		}
	    		else if (result.equals("Color"))
	    		{
	    			fg.makeColor();
	    			repaint();
	    		}
	    	}
	    };
	    // B&W or Color radio button
	    CheckboxGroup colorORbwGroup = new CheckboxGroup();
	    Checkbox BWChkBox = new Checkbox("B&W",colorORbwGroup,true);
	    Checkbox colorChkBox = new Checkbox("Color",colorORbwGroup,false);
	    BWChkBox.addItemListener(BWorColorChkBoxListener);
	    colorChkBox.addItemListener(BWorColorChkBoxListener);
	    rightToolbar.add(BWChkBox);
	    rightToolbar.add(colorChkBox);
	    
	    add(rightToolbar, BorderLayout.EAST);
	    
	    	    
	    
//		Bottom toolbar
	    Panel bottomToolbar = new Panel();
	    bottomToolbar.setLayout(new FlowLayout(FlowLayout.LEFT));
	    
	    timeForCalcLabel = new Label();
	    bottomToolbar.add(timeForCalcLabel);
	    
	    percentCompleteLabel = new Label();
	    bottomToolbar.add(percentCompleteLabel);
	    add(bottomToolbar, BorderLayout.SOUTH);
	    
	}

}